Skip to content

fix: five defects found by building a real application on 26.09.1 (release 26.09.2) - #2

Merged
ancongui merged 1 commit into
mainfrom
fix/dworkers-findings
Sep 10, 2026
Merged

fix: five defects found by building a real application on 26.09.1 (release 26.09.2)#2
ancongui merged 1 commit into
mainfrom
fix/dworkers-findings

Conversation

@ancongui

Copy link
Copy Markdown
Contributor

Five defects found while building a production application (dworkers) against 26.09.1. Every one was
reproduced by a failing test first, and every one is a case where the framework's behaviour contradicted
what its own documentation or error messages said it did.

BREAKING

packages/cqrs — the wrappers now carry the cause's error code.
CommandProcessingException / QueryProcessingException copied a FireflyException cause's httpStatus,
category and severity, then overwrote its errorCode with their own. Three quarters of a fault's
identity survived the bus and the quarter clients actually branch on did not: a duplicate came back as
409 COMMAND_PROCESSING_ERROR, a missing row as 404 COMMAND_PROCESSING_ERROR, a denial as
403 COMMAND_PROCESSING_ERROR. Applications worked around it by catching the wrapper and rethrowing
getPrevious() in every controller that dispatched a command. A cause that is not a FireflyException
still yields the generic code, so internal failures do not start leaking codes.

The Lumen capstone's own security assertion moves from COMMAND_PROCESSING_ERROR to ACCESS_DENIED in this
PR — that is the fix demonstrating itself.

packages/container — an explicit binding now wins over the scan.
ContainerRegistrar::bindClass() rebound every #[Component] class unconditionally during boot(), after
providers' register(). An application that had deliberately bound a component — the normal way to hand one
a value the container cannot autowire — silently lost that binding, and the loss surfaced nowhere near its
cause: boot succeeded and the first consumer died with Unresolvable dependency resolving [Parameter #0 [ <required> string $x ]]. This is the precedence rule the bean sweep already applied where a #[Bean]
name and a component name collide.

Fixed

firefly:cache can run on an application that has no manifests yet. With
firefly.security.method.strict on and no compiled security-methods.php, SecurityWiringProvider refused
to boot — including for firefly:cache, the only command that writes that file. Its own error message said
"Run php artisan firefly:cache", and that command hit the same error, so a fresh clone, a cleared cache
directory and the first layer of an image build were unrecoverable without disabling strict mode by hand.

A stale compiled manifest no longer bricks the command that would replace it. Compiled artefacts are
treated as absent while firefly:cache is the running command, so a component.php naming a class that has
stopped being autowirable is ignored rather than eagerly resolved before the writer is reached.
EagerSingletonsPass already tolerated an entry whose class no longer exists; this closes the neighbouring
case where the class exists and the manifest is out of date. Both now recover with firefly:cache instead of
rm -rf bootstrap/cache/firefly.

Both are one mechanism: AppScan::regenerating().

composer create-project firefly/skeleton ships its test scaffold again. The skeleton's .gitattributes
carried /tests export-ignore — right for a library, wrong for a project template, because Composer honours
it when exporting into the new project. Every scaffolded application arrived with a phpunit.xml pointing at
tests, an autoload-dev mapping Tests\ to tests/, and no tests/ directory, so the first
vendor/bin/phpunit fatalled with Trait "Tests\CreatesApplication" not found before running one assertion.

Verification

Gate Result
vendor/bin/pest 2090 passed, 6 skipped, 0 failed
vendor/bin/phpstan analyse No errors
vendor/bin/pint --test passed
vendor/bin/deptrac analyse 2285 allowed, 0 errors, 0 warnings

Version bumped to 26.09.2 with a CHANGELOG entry. The tag is deliberately not pushed: docs/publishing.md
requires a human to confirm each irreversible step.

Every one reproduced by a failing test first.

- cqrs: CommandProcessingException/QueryProcessingException dropped the cause's
  errorCode while copying its httpStatus, category and severity, so every domain
  fault reached the client as COMMAND_PROCESSING_ERROR and callers had nothing to
  branch on. BREAKING.
- container: ContainerRegistrar rebound every #[Component] class unconditionally
  during boot(), silently discarding a binding the application had registered in
  register(). The failure surfaced later as an unrelated-looking autowiring error.
  Explicit bindings now win, matching the bean sweep's own precedence rule. BREAKING.
- security/context: strict method security refused the boot of `firefly:cache`
  itself, so the command its own error message told you to run could never run.
  AppScan::regenerating() now reports that boot and the gate stands down for it.
- context: a stale compiled manifest killed EagerSingletonsPass before
  `firefly:cache` could replace it, leaving `rm -rf bootstrap/cache/firefly` as the
  only recovery. Compiled artefacts are ignored while regenerating.
- skeleton: `/tests export-ignore` deleted the test scaffold from every scaffolded
  project, so a fresh `composer create-project` fatalled on a missing
  Tests\CreatesApplication trait before running one assertion.

Release 26.09.2.
@ancongui
ancongui merged commit 84a0a8e into main Sep 10, 2026
5 checks passed
@ancongui
ancongui deleted the fix/dworkers-findings branch September 10, 2026 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant